/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package ec.cd.server;

import ec.cd.common.Networking;
import ec.cd.common.Sensors;

import java.io.*;
import java.util.ArrayList;
import java.util.Collections;

/**
 *
 * @author endri
 */
public class ContextDetection {

    private float[] accRangesTorso; //min, max (sitting), min, max (walking), min, max (running) 18 values in total
    private float[] gyrRangesTorso; //min, max (sitting), min, max (walking), min, max (running) 18 values in total
    private float[] accRangesArm;
    private float[] gyrRangesArm;
    private float[] accRangesLeg;
    private float[] gyrRangesLeg;

    /**
     *
     * @param accRanges
     * @param gyrRanges
     */
    public ContextDetection(float[] accRangesTorso, float[] gyrRangesTorso, float[] accRangesArm, float[] gyrRangesArm, float[] accRangesLeg, float[] gyrRangesLeg) {
        this.accRangesTorso = accRangesTorso;
        this.gyrRangesTorso = gyrRangesTorso;
        this.accRangesArm = accRangesArm;
        this.gyrRangesArm = gyrRangesArm;
        this.accRangesLeg = accRangesLeg;
        this.gyrRangesLeg = gyrRangesLeg;
    }

    /**
     *
     */
    public ContextDetection() {
    }
    /**
     *
     */
    public static int UNKNOWN_STATE = 0;
    /**
     *
     */
    public static int SITTING = 1;
    /**
     *
     */
    public static int WALKING = 2;
    /**
     *
     */
    public static int RUNNING = 3;

    /**
     *
     * @param stateCalibrating
     */
    public void calibrate(ArrayList<Sensors.SensorData> accData, ArrayList<Sensors.SensorData> gyrData, int stateCalibrating, Networking.BodyLocation bodyLocation) {
        //ToDo: calibrating the ranges according to a recording on the files


        //Calibrate acc values and write them into accRanges.txt
        calibrateAcc(accData, stateCalibrating, bodyLocation);
        //Calibrate gyro values and write them into gyroRanges.txt
        calibrateGyr(gyrData, stateCalibrating, bodyLocation);

    }

    /**
     *
     * @param sensorData
     * @return
     */
    public int checkContext(Sensors.SensorData sensorData[], Networking.BodyLocation bodyLocation) {
        //ToDo: check the context for the data inputed

        float[] sensorVal;
        int contextResOfAcc = 0;
        int contextResOfGyro;
        float accuracyRes;
        float[] gyrRanges = null, accRanges = null;
        float accX = 0, gyroX = 0;
        switch (bodyLocation) {
            case Torso:
                gyrRanges = gyrRangesTorso;
                accRanges = accRangesTorso;
                break;
            case Arm:
                gyrRanges = gyrRangesArm;
                accRanges = accRangesArm;
                break;
            case Leg:
                gyrRanges = gyrRangesLeg;
                accRanges = accRangesLeg;
                break;
        }

        for (int i = 0; i < sensorData.length; i++) {
            if (sensorData[i].sensorType == 1) // Gyro
            {
                sensorVal = sensorData[i].values;
                gyroX = sensorData[i].values[0];
                for (int z = 0; z < gyrRanges.length; z++) {
                    if (sensorVal[0] > gyrRanges[0] && sensorVal[0] < gyrRanges[1]) {
                        if (sensorVal[1] > gyrRanges[2] && sensorVal[1] < gyrRanges[3]) {
                            if (sensorVal[2] > gyrRanges[4] && sensorVal[2] < gyrRanges[5]) {
                                contextResOfAcc = 1;
                            }
                        }
                    } else if (sensorVal[0] > gyrRanges[6] && sensorVal[0] < gyrRanges[7]) {
                        if (sensorVal[1] > gyrRanges[8] && sensorVal[1] < gyrRanges[9]) {
                            if (sensorVal[2] > gyrRanges[10] && sensorVal[2] < gyrRanges[11]) {
                                contextResOfAcc = 2;
                            }
                        }
                    } else if (sensorVal[0] > gyrRanges[12] && sensorVal[0] < gyrRanges[13]) {
                        if (sensorVal[1] > gyrRanges[14] && sensorVal[1] < gyrRanges[15]) {
                            if (sensorVal[2] > gyrRanges[16] && sensorVal[2] < gyrRanges[17]) {
                                contextResOfAcc = 3;
                            }
                        }
                    }

                }
            } else if (sensorData[i].sensorType == 2) // Acc
            {
                sensorVal = sensorData[i].values;
                accX = sensorData[i].values[0];
                for (int z = 0; z < accRanges.length; z++) {
                    if (sensorVal[0] > accRanges[0] && sensorVal[0] < accRanges[1]) {
                        if (sensorVal[1] > accRanges[2] && sensorVal[1] < accRanges[3]) {
                            if (sensorVal[2] > accRanges[4] && sensorVal[2] < accRanges[5]) {
                                contextResOfGyro = 1;
                            }
                        }
                    } else if (sensorVal[0] > accRanges[6] && sensorVal[0] < accRanges[7]) {
                        if (sensorVal[1] > accRanges[8] && sensorVal[1] < accRanges[9]) {
                            if (sensorVal[2] > accRanges[10] && sensorVal[2] < accRanges[11]) {
                                contextResOfGyro = 2;
                            }
                        }
                    } else if (sensorVal[0] > accRanges[12] && sensorVal[0] < accRanges[13]) {
                        if (sensorVal[1] > accRanges[14] && sensorVal[1] < accRanges[15]) {
                            if (sensorVal[2] > accRanges[16] && sensorVal[2] < accRanges[17]) {
                                contextResOfGyro = 3;
                            }
                        }
                    }

                }
            }

            float resultOfAccuracy = calculateAccuracy(accX, gyroX, bodyLocation, contextResOfAcc);

            String result = "Context is detected as " + contextResOfAcc
                    + "\n and the Accuracy is :" + resultOfAccuracy;
        }





        return 0;
    }

    //accX is the x value of the Accuracy, gyroX is the x value of the Gyro sensor data
    public float calculateAccuracy(float accX, float gyroX, Networking.BodyLocation bodyLocation, int context) {
        float[] gyrRanges = null, accRanges = null;
        float xAvrgAcc = 0;
        float xAvrgGyro = 0;
        switch (bodyLocation) {
            case Torso:
                gyrRanges = gyrRangesTorso;
                accRanges = accRangesTorso;
                break;
            case Arm:
                gyrRanges = gyrRangesArm;
                accRanges = accRangesArm;
                break;
            case Leg:
                gyrRanges = gyrRangesLeg;
                accRanges = accRangesLeg;
                break;
        }
        if (context == 1) {
            xAvrgAcc = accRanges[2]; // avr X value of Sitting
            xAvrgGyro = gyrRanges[2];
        } else if (context == 2) {
            xAvrgAcc = accRanges[11];// avr X value of Walking
            xAvrgGyro = gyrRanges[11];
        } else if (context == 3)// avr X value of Running
        {
            xAvrgAcc = accRanges[20];
            xAvrgGyro = gyrRanges[20];
        }


        double accuracyRateAccX, accuracyRateGyroX, accuracyRateTotal;
        //double acceptedValAccX = (xAccMin + xAccMax) / 2;
        double acceptedValAccX = xAvrgAcc;

        accuracyRateAccX = 1.0 - ((Math.abs(acceptedValAccX - accX) / acceptedValAccX));

        System.out.println(accuracyRateAccX);

        //double acceptedValGyroX = (xGyroMin + xGyroMax) / 2.0;
        double acceptedValGyroX = xAvrgGyro;
        accuracyRateGyroX = 1.0 - ((Math.abs(acceptedValGyroX - gyroX) / acceptedValGyroX));
        System.out.println(accuracyRateGyroX);

        accuracyRateTotal = 1.0 - ((1.0 - accuracyRateAccX) * (1.0 - accuracyRateGyroX));
        float accuracyResult = (float) accuracyRateTotal;

        return accuracyResult;
    }
    //sensorAcc.txt file that is full of sensor inputs is turned into accRanges.txt

    /**
     *
     * @param stateCalibrating
     */
    public static void calibrateAcc(ArrayList<Sensors.SensorData> accData, int stateCalibrating, Networking.BodyLocation bodyLocation) {
        ArrayList<Double> tiltX = new ArrayList<Double>();
        ArrayList<Double> tiltY = new ArrayList<Double>();
        ArrayList<Double> tiltZ = new ArrayList<Double>();

        double p1, p2, n;
        int resMin, resMax;
        String accRangesFile = "";
        double avrSensorValueX;
        double avrSensorValueY;
        double avrSensorValueZ;

        try {
            for (Sensors.SensorData sensorData : accData) {
                // Print the content on the console
                double x, y, z, totalAcc;
                x = sensorData.values[0] / 9.80665;
                y = sensorData.values[1] / 9.80665;
                z = sensorData.values[2] / 9.80665;
                totalAcc = Math.sqrt(x * x + y * y + z * z);
                tiltX.add(Math.asin(x / totalAcc));
                tiltY.add(Math.asin(y / totalAcc));
                tiltZ.add(Math.asin(z / totalAcc));
            }

            p1 = 0.05;
            p2 = 0.95;
            Collections.sort(tiltX);
            Collections.sort(tiltY);
            Collections.sort(tiltZ);

            avrSensorValueX = takeAvg(tiltX);
            avrSensorValueY = takeAvg(tiltY);
            avrSensorValueZ = takeAvg(tiltZ);
            resMin = (int) Math.round((p1 * tiltX.size()) + 1 / 2f);
            resMax = (int) Math.round((p2 * tiltX.size()) + 1 / 2f);
            String status = "";
            if (stateCalibrating == 1) {
                status = "Sitting";
            } else if (stateCalibrating == 2) {
                status = "Walking";
            } else if (stateCalibrating == 3) {
                status = "Running";
            }

            accRangesFile = status + ":\n" + tiltX.get(resMin - 1) + ", " + tiltX.get(resMax - 1) + ", " + avrSensorValueX + "\n";
            accRangesFile += tiltY.get(resMin - 1) + ", " + tiltY.get(resMax - 1) + ", " + avrSensorValueY + "\n";
            accRangesFile += tiltZ.get(resMin - 1) + ", " + tiltZ.get(resMax - 1) + ", " + avrSensorValueZ + "\n";

            writeToFile(accRangesFile, "acc", bodyLocation);
        } catch (Exception e) {//Catch exception if any
            System.err.println("Error in calibration: " + e.getMessage());
            e.printStackTrace();
        }
    }

    //sensorGyro.txt file that is full of sensor inputs is turned into gyroRanges.txt
    /**
     *
     * @param stateCalibrating
     */
    public static void calibrateGyr(ArrayList<Sensors.SensorData> gyrData, int stateCalibrating, Networking.BodyLocation bodyLocation) {

        ArrayList<Double> gyrX = new ArrayList<Double>();
        ArrayList<Double> gyrY = new ArrayList<Double>();
        ArrayList<Double> gyrZ = new ArrayList<Double>();
        double p1, p2, n;
        int resMin, resMax;
        String gyrRange = "";
        double avrSensorValueX;
        double avrSensorValueY;
        double avrSensorValueZ;
        try {
            for (Sensors.SensorData sensorData : gyrData) {
                gyrX.add((double) sensorData.values[0]);
                gyrY.add((double) sensorData.values[1]);
                gyrZ.add((double) sensorData.values[2]);

            }

            p1 = 0.05;
            p2 = 0.95;
            Collections.sort(gyrX);
            Collections.sort(gyrY);
            Collections.sort(gyrZ);

            avrSensorValueX = takeAvg(gyrX);
            avrSensorValueY = takeAvg(gyrY);
            avrSensorValueZ = takeAvg(gyrZ);
            resMin = (int) Math.round((p1 * gyrX.size()) + 1 / 2f);
            resMax = (int) Math.round((p2 * gyrX.size()) + 1 / 2f);
            String status = "";
            if (stateCalibrating == 1) {
                status = "Sitting";
            } else if (stateCalibrating == 2) {
                status = "Walking";
            } else if (stateCalibrating == 3) {
                status = "Running";
            }

            gyrRange = status + ":\n" + gyrX.get(resMin - 1) + ", " + gyrX.get(resMax - 1) + ", " + avrSensorValueX + "\n";
            gyrRange += gyrY.get(resMin - 1) + ", " + gyrY.get(resMax - 1) + ", " + avrSensorValueY + "\n";
            gyrRange += gyrZ.get(resMin - 1) + ", " + gyrZ.get(resMax - 1) + ", " + avrSensorValueZ + "\n";

            writeToFile(gyrRange, "gyro", bodyLocation);
        } catch (Exception e) {//Catch exception if any
            System.err.println("Error in calibration: " + e.getMessage());
            e.printStackTrace();
        }

    }

    //write calibrated values into txts (1 for gyro, 1 for acc)
    /**
     *
     * @param text
     * @param type
     */
    public static void writeToFile(String text, String type, Networking.BodyLocation bodyLocation) {
        File file = null;
        try {
            if (type == "acc") {
                file = new File("accRanges" + bodyLocation + ".txt");
            } else if (type == "gyro") {
                file = new File("gyroRanges" + bodyLocation + ".txt");
            }
            if (!file.exists()) {
                file.createNewFile();
            }
            BufferedWriter bw = new BufferedWriter(new FileWriter(file, true));

            bw.write(text);
            bw.close();
        } catch (Exception e) {
            System.err.println("Error in calibration: " + e.getMessage());
            e.printStackTrace();
        }
    }

    // Takes the average of all recorded sensor values
    public static double takeAvg(ArrayList<Double> arrList) {
        double res;
        double total = 0;


        for (int a = 0; a < arrList.size(); a++) {
            total = total + arrList.get(a);
        }
        return total / arrList.size();

    }
}
